package com.threegroup.web.service;


import com.alibaba.fastjson2.JSON;
import com.google.common.collect.Lists;
import com.threegroup.common.dto.DishDTO;
import com.threegroup.common.exception.TakeOutException;
import com.threegroup.common.utils.AssertUtil;
import com.threegroup.common.utils.Result;
import com.threegroup.common.validator.AssertUtils;
import com.threegroup.web.dao.DishFlavorForGuestMapper;
import com.threegroup.web.dao.DishForGuestMapper;
import com.threegroup.web.dto.FlavorESDTO;
import com.threegroup.web.pojo.DishES;
import com.threegroup.web.pojo.FlavorES;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.util.QueryBuilder;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.HighlightQueryBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
public class DishForGuestService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    public static Integer TIME_OUT_CACHE = 60 * 60 * 24;
    @Resource
    private ElasticsearchRestTemplate restTemplate;
    @Resource
    private DishForGuestMapper dishForGuestMapper;
    @Resource
    private DishFlavorForGuestMapper dishFlavorForGuestMapper;
    //设置默认搜索菜名
    public static String SEARCH_NAME="菜";

    private List<String> highlightList = Lists.newArrayList("name", "description");

    /**
     * ES 多条件查询
     *
     * @param name
     * @return
     * @throws IOException
     */
    public Result<List<DishES>> queryDishForGuest(String name) throws IOException {
        List<DishES> list = new ArrayList<>();
        //设置查询条件
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //为空设置默认搜索条件
        if (!StringUtils.isBlank(name)) {

            //添加ES多条件搜索权重
            Map<String, Float> fields = new HashMap<>();
            fields.put("name", 9f);
            fields.put("description", 0.7f);
            //设置查询器
            boolQueryBuilder.should(QueryBuilders.multiMatchQuery(name, "name", "description").fields(fields));
            boolQueryBuilder.must(QueryBuilders.termQuery("isDeleted",0));//设置未删除

//            boolQueryBuilder.should(QueryBuilders.multiMatchQuery(name,"name","description").field("description",15f));
        }

        //配置查询结果显示

        // 高亮显示
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("name");
        highlightBuilder.field("description");
        highlightBuilder.preTags("<b style='color:red'>");
        highlightBuilder.postTags("</b>");

        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(boolQueryBuilder)
                .withPageable(PageRequest.of(0, 200))
                .withSort(SortBuilders.scoreSort().order(SortOrder.DESC))
                .withHighlightBuilder(highlightBuilder)
                .build();
        //执行搜索
        SearchHits<DishES> search = restTemplate.search(searchQuery, DishES.class);
        //检查遍历结果搜索
        for (SearchHit<DishES> dishESSearchHit : search) {
            //接收结果
            DishES content = dishESSearchHit.getContent();
            Map<String, List<String>> highlightMap = dishESSearchHit.getHighlightFields();
            for (String field : highlightList) {
                if (highlightMap.get(field) != null) {
                    List<String> highList = highlightMap.get(field);
                    if (highList.size() > 0) {
                        if ("name".equals(field)) {
                            content.setName(highList.get(0));
                        } else if ("description".equals(field)) {
                            content.setDescription(highList.get(0));
                        }
                    }
                }
            }
            Long id = content.getId();

            //打印搜索结果排名
            System.out.println("得分：" + dishESSearchHit.getScore() + "===内容：" + dishESSearchHit.getContent().getName() + "===描述:" + dishESSearchHit.getContent().getDescription());

            //从另外的索引里再次查询Es获取flavor信息
            //配置ES搜索条件
            BoolQueryBuilder boolQueryBuilder1 = QueryBuilders.boolQuery();
            //以dishid执行搜索
            boolQueryBuilder1.must(QueryBuilders.termQuery("dishId", id));
            //该菜品未删除
            boolQueryBuilder1.must(QueryBuilders.termQuery("isDeleted", 0));
            //建立查询
            NativeSearchQuery searchQuery1 = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder1)
                    .build();
            //执行搜索
            SearchHits<FlavorES> search1 = restTemplate.search(searchQuery1, FlavorES.class);

            //存在数据就遍历
            if (search1.hasSearchHits()) {
                //设置结果集
                List<FlavorESDTO> flavorESDTO = new ArrayList<>();
                //遍历口味集合
                for (SearchHit<FlavorES> flavorESSearchHit : search1) {
                    //遍历获取FlavorES对象
                    FlavorES content1 = flavorESSearchHit.getContent();
                    //把获取的口味对象FlavorES，转移到FlavorESDTO
                    String value = content1.getValue();
                    List<String> strings = new ArrayList<>();
                    String[] split = value.split(",");
                    for (String s : split) {
                        strings.add(s);
                    }
                    //执行数据转移
                    FlavorESDTO flavorESDTO1 = new FlavorESDTO();
                    flavorESDTO1.setDishId(content1.getDishId());
                    flavorESDTO1.setId(content1.getId());
                    flavorESDTO1.setName(content1.getName());
                    flavorESDTO1.setValue(strings);
                    flavorESDTO1.setCreator(content1.getCreator());
                    flavorESDTO1.setCreateDate(content1.getCreateDate());
                    flavorESDTO1.setUpdater(content1.getUpdater());
                    flavorESDTO1.setUpdateDate(content1.getUpdateDate());
                    flavorESDTO1.setIsDeleted(content1.getIsDeleted());
                    //添加每一条FlavorESDT对象
                    flavorESDTO.add(flavorESDTO1);
                }
                //把封装好的结果集，再封装到Flavors
                content.setFlavors(flavorESDTO);
            }
            list.add(content);
        }
        Result<List<DishES>> listResult = new Result<>();
        listResult.setData(list);
        //返回结果
        return listResult;
    }

    /**
     * 根据id 查询
     *
     * @param id1
     * @return
     */
    public Result<DishES> queryDishById(String id1) {
        //准备结果集
        Result<DishES> dishEsResult = new Result<>();
        //设置随机时间添加
        int random = (int)(Math.random()*100+1.0);
        Integer randomSecounds = (Integer)random;
        /**
         *准备redis模板
         */
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        stringRedisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        //校验传入参数非空
        String[] split = id1.split("=");
        AssertUtil.isTrue(split.length != 2, 302, "输入的Id为空");
        //有输入Id就获取id
        String idRedis = split[1];
        Long id = Long.valueOf(idRedis);


        /**
         * 首先查询redis是否存在信息
         */
        DishES dishEsFromRedis = null;
        String dish = opsForValue.get(idRedis);
        //存在就执行返回
        if (StringUtils.isNoneBlank(dish)) {
            //如果查到的是数据库里没有的空，则直接返回
            if ("{}".equals(dish)) {
                throw new TakeOutException(302, "待查询的记录不存在");
            }
            //否则正常转型并接收，返回查询的结果
            //转型并接收
            dishEsFromRedis = JSON.parseObject(dish, DishES.class);
            //添加结果
            dishEsResult.setData(dishEsFromRedis);
            //并且把该数据有效期重置1天
            stringRedisTemplate.expire(idRedis, TIME_OUT_CACHE+randomSecounds, TimeUnit.SECONDS);
            //返回
            return dishEsResult;
        }

        /**
         * 如果该热门数据不存在则直接访问数据库，则出现缓存击穿
         * 1、如果是高并发情况下，则需要把查询数据库的操作，进行加锁，
         * 2、应该是针对不同的商品id上锁，而不是简单上锁，
         * 现在多是分布式，要添加分布式锁，用setnx(setIfAbsent)实现
         */
        String sycn = idRedis + "sycn";
        Boolean test = opsForValue.setIfAbsent(sycn, "test");//true-执行成功  false-执行添加失败
        if (test != true) {
            throw new TakeOutException(303, "系统繁忙请稍后重试");
        }

        /**
         * 再次查询redis是否存在信息
         */
        dish = opsForValue.get(idRedis);
        //存在就执行返回
        //存在就执行返回
        if (StringUtils.isNoneBlank(dish)) {
            //如果查到的是数据库里没有的空，则直接返回
            if ("{}".equals(dish)) {
                throw new TakeOutException(302, "待查询的记录不存在");
            }
            //否则正常转型并接收，返回查询的结果
            //转型并接收
            dishEsFromRedis = JSON.parseObject(dish, DishES.class);
            //添加结果
            dishEsResult.setData(dishEsFromRedis);
            //并且把该数据有效期重置1天
            stringRedisTemplate.expire(idRedis, TIME_OUT_CACHE+randomSecounds, TimeUnit.SECONDS);

            /**
             *
             * 删除分布式锁
             */
            stringRedisTemplate.delete(sycn);
            //返回
            return dishEsResult;
        }


        //查询数据库
        DishES dishES = dishForGuestMapper.queryDishById(id);


        //判断数据库所查内容
        if (dishES != null) {
            //不是空就查询口味信息
            List<FlavorES> flavorESList = dishFlavorForGuestMapper.queryDishFlavorByDishId(dishES.getId());

            //有口味信息就添加，否则不管
            if (flavorESList != null && flavorESList.size()!=0 ) {
                //准备List集合
                List<FlavorESDTO> list = new ArrayList<>();
                //把查询到的结果遍历
                for (FlavorES flavorES : flavorESList) {

                    //结果转移到FlavorESDTO对象
                    FlavorESDTO flavorESDTO = new FlavorESDTO();
                    //分解value称list集合
                    String value = flavorES.getValue();
                    List<String> strings = new ArrayList<>();
                    String[] split1 = value.split(",");
                    for (String s : split1) {
                        strings.add(s);
                    }

                    //转移每一条信息
                    flavorESDTO.setId(flavorES.getId());
                    flavorESDTO.setDishId(flavorES.getDishId());
                    flavorESDTO.setName(flavorES.getName());
                    flavorESDTO.setValue(strings);
                    flavorESDTO.setCreator(flavorES.getCreator());
                    flavorESDTO.setCreateDate(flavorES.getCreateDate());
                    flavorESDTO.setUpdater(flavorES.getUpdater());
                    flavorESDTO.setUpdateDate(flavorES.getUpdateDate());
                    flavorESDTO.setIsDeleted(flavorES.getIsDeleted());

                    //添加进list
                    list.add(flavorESDTO);
                }
                //把集合封装进对象
                dishES.setFlavors(list);
            }
            /**
             * 把查到的结果备份到redis
             */
            String JsondishES = JSON.toJSONString(dishES);

            opsForValue.set(idRedis, JsondishES, TIME_OUT_CACHE+randomSecounds, TimeUnit.SECONDS);//设置有效时间1天
        } else {
            /**
             * 没有查到防止再次查数据库，直接设置一个空值在redis,60秒
             */
            opsForValue.set(idRedis, "{}", 60, TimeUnit.SECONDS);
            AssertUtils.isNull(dishES, 303, "待查询的记录不存在");

        }


        /**
         *
         * 删除分布式锁
         */
        stringRedisTemplate.delete(sycn);


        //添加结果
        dishEsResult.setData(dishES);
        //返回
        return dishEsResult;
    }

    /**
     * 根据es
     *
     * @param categoryId
     * @param status
     * @return
     */
    public Result<List<DishES>> queryDishForGuestByCaIdAndStatus(String categoryId, String status) {
        List<DishES> list = new ArrayList<>();
        //设置查询条件
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //为空设置默认搜索条件
        if (!StringUtils.isBlank(categoryId)) {
            boolQueryBuilder.must(QueryBuilders.termQuery("categoryId", categoryId));//设置该品类Id
        }
        if (!StringUtils.isBlank(status)) {
            boolQueryBuilder.must(QueryBuilders.termQuery("status", status));//设置启动状态
        }
        boolQueryBuilder.must(QueryBuilders.termQuery("isDeleted", 0));//设置未删除
        //配置查询结果显示
        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(boolQueryBuilder)
                .withPageable(PageRequest.of(0, 200))
                .withSort(SortBuilders.scoreSort().order(SortOrder.DESC))
                .build();
        //查询Es
        SearchHits<DishES> dishESSearchHits = restTemplate.search(searchQuery, DishES.class);
        //遍历查询结果集
        for (SearchHit<DishES> dishESSearchHit : dishESSearchHits) {
            //接收结果
            DishES content = dishESSearchHit.getContent();
            Long id = content.getId();

            //打印搜索结果排名
            System.out.println("得分：" + dishESSearchHit.getScore() + "===内容：" + dishESSearchHit.getContent().getName() + "===描述:" + dishESSearchHit.getContent().getDescription());

            //从另外的索引里再次查询Es
            //配置ES搜索条件
            BoolQueryBuilder boolQueryBuilder1 = QueryBuilders.boolQuery();
            //以dishid执行搜索
            boolQueryBuilder1.must(QueryBuilders.termQuery("dishId", id));
            boolQueryBuilder1.must(QueryBuilders.termQuery("isDeleted", 0));
            //建立查询
            NativeSearchQuery searchQuery1 = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder1)
                    .build();
            //执行搜索
            SearchHits<FlavorES> search1 = restTemplate.search(searchQuery1, FlavorES.class);
            //存在数据就遍历
            //并把结果集转移
            if (search1.hasSearchHits()) {
                //准备List集合
                List<FlavorESDTO> flavorESDTOList = new ArrayList<>();
                //遍历口味集合,转移数据到FlavorESDTO对象，并添加到flavorESDTOList集合
                for (SearchHit<FlavorES> flavorESSearchHit : search1) {
                    //遍历获取FlavorES对象
                    FlavorES content1 = flavorESSearchHit.getContent();
                        //结果转移到FlavorESDTO对象
                        FlavorESDTO flavorESDTO = new FlavorESDTO();
                        //分解value称list集合
                        String value = content1.getValue();
                        List<String> strings = new ArrayList<>();
                        String[] split1 = value.split(",");
                        for (String s : split1) {
                            strings.add(s);
                        }
                        //转移每一条信息
                        flavorESDTO.setId(content1.getId());
                        flavorESDTO.setDishId(content1.getDishId());
                        flavorESDTO.setName(content1.getName());
                        flavorESDTO.setValue(strings);
                        flavorESDTO.setCreator(content1.getCreator());
                        flavorESDTO.setCreateDate(content1.getCreateDate());
                        flavorESDTO.setUpdater(content1.getUpdater());
                        flavorESDTO.setUpdateDate(content1.getUpdateDate());
                        flavorESDTO.setIsDeleted(content1.getIsDeleted());

                        //添加进list
                    flavorESDTOList.add(flavorESDTO);
                    }
                //把封装好的结果集，再封装到Flavors
                content.setFlavors(flavorESDTOList);
                }
            //添加list
            list.add(content);
        }
        //设置结果集并返回
        Result<List<DishES>> listResult = new Result<>();
        listResult.setData(list);
        //返回结果
        return listResult;


    }
}
